; docformat = 'rst'
;
; NAME:
;       CDF_Read__DEFINE
;
;*****************************************************************************************
;   Copyright (c) 2013, Matthew Argall                                                   ;
;   All rights reserved.                                                                 ;
;                                                                                        ;
;   Redistribution and use in source and binary forms, with or without modification,     ;
;   are permitted provided that the following conditions are met:                        ;
;                                                                                        ;
;       * Redistributions of source code must retain the above copyright notice,         ;
;         this list of conditions and the following disclaimer.                          ;
;       * Redistributions in binary form must reproduce the above copyright notice,      ;
;         this list of conditions and the following disclaimer in the documentation      ;
;         and/or other materials provided with the distribution.                         ;
;       * Neither the name of the <ORGANIZATION> nor the names of its contributors may   ;
;         be used to endorse or promote products derived from this software without      ;
;         specific prior written permission.                                             ;
;                                                                                        ;
;   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY  ;
;   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ;
;   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT  ;
;   SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       ;
;   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ;
;   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR   ;
;   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN     ;
;   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN   ;
;   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  ;
;   DAMAGE.                                                                              ;
;*****************************************************************************************
;
; PURPOSE:
;+
;       A class for reading CDF files.
;
; :Author:
;       Matthew Argall::
;		University of New Hampshire
;		Morse Hall, Room 113
;       8 College Rd.
;		Durham, NH, 03824
;       matthew.argall@wildcats.unh.edu
;
; :Categories:
;       CDF Utilities, File I/O, Data Reader
;
; :Uses:
;   Uses the following programs::
;       cgErrorMSG.pro (Coyote Graphics)
;       CDF_VarSelect__Define.pro
;       convert_time.pro
;
; :History:
;   Modification History::
;       11/11/2012  -   Written by Matthew Argall
;       04/03/2013  -   MAXREC is 0-based, so 1 had to be added in many places in order
;                           to retrieve the correct number of records.
;       04/14/2013  -   Removed ::CheckElements, added ::fetchData, ::plot_setup, and
;                           ::ts_struct. - MRA
;       04/17/2013  -   Not all CDF files honor the
;                           VARY/NOVARY convention, so check to see if the dependent 
;                           variables have the same number of records as the variable that
;                           they depend on. If not, get all of the records, independent
;                           of the inputs. This prevents the CDF file from filling the
;                           extra elements with a "padvalue" - MRA
;       06/27/2013  -   Added a GUI for data variable selection. Added CATDESC, VAR_TYPE,
;                           and VAR_NOTES to the variable attribute configuration fields.
;                           Added the ::Plot_Setup and ::getMetaData methods. - MRA
;                       Made the banners above the widget lists a fixed width. Using the
;                           /DYNAMIC_RESIZE keyword, the GUI was moved back to its
;                           original position whenever the label was changed. IDL news
;                           groups says this is a bug. - MRA
;       07/10/2013  -   3D spectrogram data can now be plotted. The results form Sort_Depend
;                           are not all that straight-forward, so I added a lengthy
;                           description. - MRA
;       07/23/2013  -   If there is no data in the given time interval (i.e. REC_COUNT=0),
;                           then !Null is returned as data. - MRA
;       07/30/2013  -   Tree events were returning variables in 1-element arrays, which
;                           then caused subhashes to be extracted from hashes. Fixed. - MRA
;       08/04/2013  -   Realized that ordering data so that DEPEND_1 corresponds to
;                           dimension 1 of `DATA` messes up the order of the outputs.
;                           Added ORGANIZE keyword if that is the desired behavior. This
;                           is useful for automatic plotting. - MRA
;       08/08/2013  -   Separated the GUI and plotting methods into their own classes:
;                           CDF_Select__Define.pro and CDF_Plot__Define.pro. - MRA
;       09/08/2013  -   Added DISPLAY_TYPE to the list of CLUSTER-specific configuration
;                           values. - MRA
;       09/15/2013  -   In INIT, try to recognize Cluster file names so that the CLUSTER
;                           flag can be set in the call to Set_Config. - MRA.
;       2013/11/16  -   Catch when MAXREC=0. Make !Null backwards compatible with
;                           MrNull.pro. Added the MAXREC keyword as a means to test if data
;                           was read. - MRA
;       2014/02/20  -   REC_COUNT had been marked as an output. - MRA
;       2014/03/06  -   Depend_0 is no longer read twice if a /REC_TIMES is set and
;                           the DEPEND_0 parameter is given.
;-
;*****************************************************************************************
;+
;   The purpose of this method is to destroy the object.
;-
pro CDF_Read::destroy
    obj_destroy, self
end


;+
;       The purpose of this method is to get the record number of a specified EPOCH or 
;       EPOCH16 time
;
; :Private:
;
; :Params:
;
;       TVARNAME:           in, required, type=string
;                           Name associated with the time variable
;       EPOCH_RANGE:        in, required, type=CDF_EPOCH/EPOCH16/TT2000/STRING.
;                           The [min, max] time range for which `REC_NUM` is to be returned.
;                               Can be any CDF epoch type.
;
; :Keywords:
;
;       DATETIME:           in, optional, type=Boolean. Default=0.
;                           Indicate that `EPOCH_VAL` (and possibly TIME) hold ISO-1806
;                               strings, not epoch values.
;       SSM:                in, optional, type=Boolean. Default=0.
;                           Indicate that EPOCH_VAL and TIME hold date and seconds since
;                               midnight values, respectively, not EPOCH values.
;       TIME                out, optional, type=double/dblcomplex/long64
;                           Epoch times between `EPOCH_RANGE`
;       ZVARIABLE:          in, optional, type=Boolean, default=0
;                           If time_var is a variable number, then set
;                               ZVARIABLE = 0 if it is a regular variable
;                               ZVARIABLE = 1 if it is a zvariable
;
; :Returns:
;
;       REC_NUM:            The record number within the CDF file at which to find TIME
;-
function CDF_Read::time_recnum, tVarName, epoch_range, $
DATETIME=datetime, $
SSM=SSM, $
TIME=time, $
ZVARIABLE = zvariable
	compile_opt strictarr
	on_error, 2

;---------------------------------------------------------------------
;Check Inputs \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

    ;Make sure TIME_VAR is a name, not a number
    if size(tVarName, /TNAME) ne 'STRING' then message, 'TIME_VAR must be a variable name.'
	
	nt = n_elements(epoch_range)

;---------------------------------------------------------------------
;Convert to Epoch Times \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

	;Check if the given time needs to be converted to an EPOCH type
	if keyword_set(datetime) or keyword_set(ssm) then begin
        ;get a single sample
        cdf_varget, self.cdf_id, tVarName, time_sample
        	
	    ;check which type of epoch to convert it to
	    epoch_type = MrCDF_Epoch_Type(time_sample)
	    case epoch_type of
	        'CDF_EPOCH': to_cdfepoch = 1
	        'CDF_EPOCH16': to_epoch16 = 1
	        'CDF_TIME_TT2000': to_tt2000 = 1
	        else: message, 'Epoch type "' + epoch_type + '" is not a known CDF EPOCH type.'
	    endcase

	    ;get the epoch time
	    epoch_time = convert_time(epoch_range, time, $
	                              SSM=ssm, DATETIME=datetime, /DISSECT, $
	                              TO_CDFEPOCH=to_cdfepoch, TO_EPOCH16=to_epoch16, $
	                              TO_TT2000=to_tt2000)

	;if not, then the epoch time to look for is stored in EPOCH_RANGE
	endif else epoch_time = epoch_range

;---------------------------------------------------------------------
;Find Record Numbers \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

	;Get the time data
	time = self -> Read(tVarName)

	;Epoch16 values are complex with the real part being the epoch time down to the second
	;and the complex part being the pico seconds (.mmmuuunnnppp e12)
	rec_num = lonarr(nt)
	if epoch_type eq 'CDF_EPOCH16' then begin
	    for i = 0, nt - 1 do begin
            ;find all of the record numbers that match TIME down to the second
            this_sec = where(real_part(time) eq real_part(epoch_time[i]), count)
            
            ;if there are no exact matches, then use VALUE_LOCATE() to find the nearest time
            if count eq 0 then begin
                rec_num[i] = value_locate(real_part(time), real_part(epoch_time[i]))
            
            ;otherwise search through the matching cases
            endif else begin
                ;Weed through those times and pick the closest one to TIME.
                this_pico = value_locate(imaginary(time[this_sec]), imaginary(epoch_time[i]))
                
                ;If THIS_PICO = -1, then TIME[I] < DATA[THIS_SEC[0]] and we want to take the
                ;last record from the previous second to be consistent with VALUE_LOCATE().
                ;Ohterwise, take the indicated record.
                if this_pico eq -1 then rec_num[i] = this_sec[0] - 1 $
                                   else rec_num[i] = this_sec[this_pico]
            endelse
        endfor

	;for regular epoch values...
	;value locate will return the index number of TIME within the TIME_VARNAME
	;data, /rounded down/ to the nearest element in TIME_VARNAME
	endif else begin
	    if n_elements(time) eq 1 $
	        then rec_num = [0,0] $
	        else rec_num = value_locate(time, epoch_time)
	endelse

;---------------------------------------------------------------------
;Round Properly \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

    ;If Value_Locate() rounded down, then increase the starting rec number by 1.
    rec_num[0] += time[rec_num[0]] lt epoch_time[0]

    ;If either the start or end time were before the beginning of the data interval,
    ;Value_Locate() will return -1. Add 1 to make them 0.
    rec_num = (rec_num eq -1) + rec_num
    rec_num = rec_num < (n_elements(time) - 1)

    ;Trim the time data
    time = time[*, rec_num[0]:rec_num[1]]

	return, rec_num
end


;+
;   The purpose of this method is to read the requested data from the CDF file.
;
;   :Private:
;
;   :Params:
;       VARIABLE:      in, required, type=string/int
;                      Name or number of the variable whose data is to be read, or
;                           a named variable into which variable names are to be
;                           returned. In the latter case, a GUI will appear asking
;                           to choose from a list of variable contained in the file.
;       REC_START:      in, out, optional, type=long, default=0
;                       The record at which to start reading data. If `REC_TIME` is set,
;                           then this is the time, in cdf epoch format, of the first data
;                           point to be read. `DEPEND_0` is then assumed to be time and
;                           of a matching cdf epoch type.
;       REC_COUNT:      in, optional, type=Long/lonarr
;                       The number of records read from each variable
;
; :Keywords:
;       INDEP_MAXREC:   in, optional, type=long, default=maxrec of `VARIABLE`
;                       If `VARIABLE` is a DEPEND_# variable, then use this keyword
;                           indicates the maximum record number of the variable on which
;                           `VARIABLE` depends. This is used to help determine how many
;                           records to read when the record variance is not managed
;                           properly within the CDF file.
;       ZVARIABLE:      in, optional, type=Boolean, default=0
;                       If time_var is a variable number, then set
;                               ZVARIABLE = 0 if it is a regular variable
;                               ZVARIABLE = 1 if it is a zvariable
;                           If ZVARIABLE has 1 element, all variables are assumed to be
;                           of the same type.
;       _REF_EXTRA:     in, optional, type=structure
;                       A structure of keywords accepted by CDF_VARGET
;
;   :Returns:
;       DATA:           Data corresponding to `VARIABLE`.
;-
function CDF_Read::VarGet, variable, rec_start, rec_count, $
INDEP_MAXREC = indep_maxrec, $
ZVARIABLE=zvariable, $
_REF_EXTRA=extra
    compile_opt idl2
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, MrNull(-1)
    endif

    ;Get the record variance and the maximum number of records
    self -> get_varinfo, variable, RECVAR=recvar, MAXREC=maxrec, ZVARIABLE=zvariable
    if n_elements(indep_maxrec) eq 0 then indep_maxrec = maxrec
    
    ;If the variable has a record variance of NOVARY, then get 1 record,
    ;otherwise get them all.
    if (recvar eq 'NOVARY') then begin
        rstart = 0
        rcount = 1
    endif else begin
        if maxrec eq indep_maxrec then begin
            rstart = rec_start
            rcount = rec_count
        endif else begin
            rstart = 0
            rcount = maxrec + 1
        endelse
    endelse

    ;If there is no data within the given time interval, then return.
    if rcount eq 0 then return, MrNull(-1)

    ;Retrieve the data
    cdf_varget, self.cdf_id, variable, data, REC_START=rstart, REC_COUNT=rcount, $
                ZVARIABLE=zvariable, _STRICT_EXTRA=extra
    
    return, data
end


;+
;       The purpose of this program is to read the data of the specified variable
;
; :Params:
;
;       VARIABLE:           in, required, type=string/strarr(*)
;                           Name of the variable whose data is to be read.
;       DEPEND_0:           out, optional, type=depends
;                           Data for the 0-th dependent variable
;       DEPEND_1:           out, optional, type=depends
;                           Data for the 1-st dependent variable
;       DEPEND_2:           out, optional, type=depends
;                           Data for the 2-nd dependent variable
;       DEPEND_3:           out, optional, type=depends
;                           Data for the 3-rd dependent variable
;
; :Keywords:
;
;       QUIET:          in, optional, type=Boolean, default=1
;                       Turn on the system variable !QUIET so that annoying CDF messages
;                           are not output to the terminal.
;       GROUP_LEADER:   in, optional, type=integer
;                       The group leader of the variable selection gui. Use only when
;                           `VARIABLE` is given and undefined.
;       MAXREC:         out, optional, type=long
;                       0-Based number of records availble. Can be used to check data
;                           could be read from the file.
;       REC_COUNT:      in, out, optional, type=Long/lonarr
;                       The number of records read from each variable. If `REC_END` is
;                           provided, then use this keyword to obtain the number of
;                           records read.
;       REC_END:        in, out, optional, type=long, default=MAXREC
;                       The record at which to stop reading data. If `REC_TIME` is set,
;                           then this is the time, in cdf epoch format, of the last data
;                           point to be read. `DEPEND_0` is then assumed to be time and
;                           of a matching cdf epoch type.
;       REC_START:      in, out, optional, type=long, default=0
;                       The record at which to start reading data. If `REC_TIME` is set,
;                           then this is the time, in cdf epoch format, of the first data
;                           point to be read. `DEPEND_0` is then assumed to be time and
;                           of a matching cdf epoch type.
;       REC_TIMES:      in, optional, type=boolean, default=0
;                       Indicate that the `REC_START` and `REC_END` are the start and
;                           end times of a data interval, not the record range. Both
;                           must be given.
;       TO_EPOCH:       in, optional, type=Boolean, default=0
;                       If `REC_TIMES` is set, then this will indicate that `REC_START`
;                           and `REC_END` are date-time strings 
;                           ('YYYY-MM-DD/HH:MM:SS.dddddddddddd' -- delimiters optional)
;                           that need to be converted to to cdf epoch values.
;       ZVARIABLE:      in, optional, type=Boolean, default=0
;                       If time_var is a variable number, then set
;                               ZVARIABLE = 0 if it is a regular variable
;                               ZVARIABLE = 1 if it is a zvariable
;                           If ZVARIABLE has 1 element, all variables are assumed to be
;                           of the same type.
;       _REF_EXTRA:     in, optional, type=structure
;                       Any keyword accepted by CDF_VARGET is also accepted for keyword
;                           inheritance.
;
; :Returns:
;       DATA:           Data corresponding to `VARIABLE`.
;-
function CDF_Read::Read, variable, depend_0, depend_1, depend_2, depend_3, $
 DEPEND_NAMES = depend_names, $
 DISPLAY_DEP = display_dep, $
 GROUP_LEADER = group_leader, $
 MAXREC = maxrec, $
 QUIET = quiet, $
 REC_START = rec_start, $
 REC_END = rec_end, $
 REC_COUNT = rec_count, $
 REC_TIMES = rec_times, $
 ZVARIABLE = zvariable, $
 TO_EPOCH = to_epoch, $
_REF_EXTRA = extra
	compile_opt idl2
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        
        !quiet = 0
        void = cgErrorMsg()
        return, MrNull(-1)
    endif

;---------------------------------------------------------------------
;Defaults \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

	if n_elements(quiet) eq 0 then quiet = 1 else quiet = keyword_set(quiet)
	zvariable = keyword_set(zvariable)
	to_epoch = keyword_set(to_epoch)
	rec_times = keyword_set(rec_times)
	
	;The CDF routines have a lot of annoying error messages.
	;This will turn them off as the data is being read
	
    ;Turn off messages from CDF_ATT_GET
    ;   % CDF_ATTGET: Warning: converting signed bytes data to signed ints.
	!Quiet = quiet

;---------------------------------------------------------------------
;Start GUI? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
	
	;How many variables were given?
	n_vars = n_elements(variable)

    ;Start a GUI to ask which variables to read.
	if n_vars eq 0 then begin
	    oVS = obj_new('CDF_VarSelect', self, GROUP_LEADER=group_leader)
	    oVS -> Get_Property, VARIABLE=variable
	    oVS -> Destroy
	    if variable eq '' then return, MrNull(-1)
	    n_vars = 1
	endif
	
	;This program only works for 1 variable so far
	if n_vars gt 1 then message, 'VARIABLE must be a scalar string.'
	
	;Make sure VARIABLE is a valid variable name
	tf_varexists = self -> VarExists(variable)
	if tf_varexists eq 0 then message, 'Variable "' + variable + '" does not exist.'

;---------------------------------------------------------------------
;Get Dependent Data? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    ;the "varHasAtt" method checks to see if the particular attribute
    ;is present in the CDF file. That does not guarantee that a variable
    ;attribute exists for the given variable. 

	n_prams = n_params()
	;Get the dependent variable names that are being requested.
	depend_names = strarr(4)
	switch n_prams of
	    5: if self -> varHasAtt(variable, (*self.config).depend_3, ATT_VALUE=att_value) $
	        then depend_names[3] = att_value
	    4: if self -> varHasAtt(variable, (*self.config).depend_2, ATT_VALUE=att_value) $
	        then depend_names[2] = att_value
	    3: if self -> varHasAtt(variable, (*self.config).depend_1, ATT_VALUE=att_value) $
	        then depend_names[1] = att_value
	    2: if self -> varHasAtt(variable, (*self.config).depend_0, ATT_VALUE=att_value) $
	        then depend_names[0] = att_value
	    else: ;Do nothing
	endswitch
	
	;If a specific time interval is desired, we need DEPEND_0.
	if n_prams lt 2 and keyword_set(rec_times) then begin
	    if self -> varHasAtt(variable, (*self.config).depend_0, ATT_VALUE=att_value) $
	        then depend_names[0] = att_value
	endif

;---------------------------------------------------------------------
;Determine Record Range \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------
    ;Get the maximum number of records
    self -> get_varinfo, variable, MAXREC=maxrec, RECVAR=recvar, ZVARIABLE=zvariable
    if strcmp(recvar, 'NOVARY') then rec_count = 1

    ;Default to starting at 0
    if n_elements(rec_start) eq 0 $
        then rec_start_out = 0 $
        else rec_start_out = rec_start
        
    ;Default to getting the maximum number of records. MAXREC is 0-based, so add 1.
    ;If REC_START and REC_END are times, then they must be specified together. Thus, if
    ;REC_END is not given, we only need REC_COUNT
    if n_elements(rec_count) eq 0 and n_elements(rec_end) eq 0 $
        then rec_count = (maxrec+1) - rec_start_out
    
    ;If REC_START and REC_END are times, convert them to record ranges.
    if keyword_set(rec_times) then begin
        ;Make sure a DEPEND_0 variable exists for the variable.
        if depend_names[0] eq '' then begin
            message, 'No DEPEND_0 for " ' + variable + '. Cannot convert REC_START to EPOCH.', /INFORMATIONAL
            message, 'Reading all records.', /INFORMATIONAL
            rec_range = [0, maxrec + 1]
        
        endif else begin
            ;Get the record range by examining the time stamps. Round the start-time up
            ;to be within the data interval.
            rec_range = self -> time_recnum(depend_names[0], [rec_start_out, rec_end], $
                                            TIME=time, DATETIME=rec_times)
            if arg_present(depend_0) eq 0 then begin
                void = temporary(time)
                void = 0
            endif else depend_0 = temporary(time)
        endelse

        rec_start_out = rec_range[0]
        rec_count = rec_range[1] - rec_range[0] + 1
    
    ;If REC_COUNT is still not defined, REC_END must contain record numbers.
    ;Compute REC_COUNT.
    endif else if n_elements(rec_count) eq 0 then begin
        rec_count = rec_end - rec_start_out
    endif

;---------------------------------------------------------------------
;Read Data \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
;---------------------------------------------------------------------

    ;Get the data. Normally, if no data is contained in the file, MaxRec=-1. However,
    ;many files place a PadValue instead, making MacRec=0. Any query for data, then,
    ;will return arrays of pad values.
    if maxrec le 0 then message, 'MAXREC <= 0. No Data available for "' + variable + '"', /INFORMATIONAL
    data = self -> VarGet(variable, rec_start_out, rec_count, ZVARIABLE=zvariable, _EXTRA=extra)

    ;Relate the DEPEND_# and LABEL_# to the proper dimensions of VNAME
    self -> get_varinfo, variable, DIM=dim

    ;Step through each dependent variable
    for i = 0, 3 do begin
        ;Skip ones that are not being returned.
        if depend_names[i] eq '' then continue
        if i eq 0 && n_elements(depend_0) gt 0 then continue

        ;Read the dependent variable data
        temp_depend = self -> VarGet(depend_names[i], rec_start_out, rec_count, $
                                     INDEP_MAXREC=maxrec, ZVARIABLE=zvariable, _EXTRA=extra)
        if MrIsNull(temp_depend, -1) then continue

        case i of
            0: depend_0 = temporary(temp_depend)
            1: depend_1 = temporary(temp_depend)
            2: depend_2 = temporary(temp_depend)
            3: depend_3 = temporary(temp_depend)
        endcase
    endfor
	
	!quiet = 0
	return, data
end


;+
;   The purpose of this method is to provide a means of getting variable attribute values
;   related to the variables in the configuration structure. Note that if the specific
;   attribute is not associated to the input variable, then !Null will be returned.
;
;   Definitions of variable attributes can be found at the NASA Goddard's
;   Space Flight Data Facility  `International Solar-Terrestrial Physics 
;   <http://spdf.gsfc.nasa.gov/istp_guide/istp_guide.html>` webpage.
;-
pro CDF_Read::Get_ConfigVals, variable, $
CATDESC = catdesc, $
DELTA_MINUS_VAR = delta_minus_var, $
DELTA_PLUS_VAR = delta_plus_var, $
DEPEND_0 = depend_0, $
DEPEND_1 = depend_1, $
DEPEND_2 = depend_2, $
DEPEND_3 = depend_3, $
DISPLAY_TYPE = display_type, $
FIELDNAM = fieldnam, $
FILLVAL = fillval, $
LABLAXIS = lablaxis, $
LABL_PTR_1 = labl_ptr_1, $
LABL_PTR_2 = labl_ptr_2, $
LABL_PTR_3 = labl_ptr_3, $
UNITS = units, $
UNIT_PTR = unit_ptr, $
VALIDMIN = validmin, $
VALIDMAX = validmax, $
VAR_TYPE = var_type, $
VAR_NOTES = var_notes, $
SCALETYP = scaletyp, $
SCAL_PTR = scal_ptr, $
SCALEMIN = scalemin, $
SCALEMAX = scalemax, $
ZVARIABLE = zvariable
    compile_opt idl2
    on_error, 2
    
    if n_params() ne 1 then message, 'VARIABLE must be provided.'
    
    ;
    ;VarHasAtt will determine if the attribute exists and if it is associated with the
    ;variable. If both of those are true, then the attribute value will be returned into
    ;the ATTVALUE keyword.
    ;
    if arg_present(catdesc)    then void = self -> VarHasAtt(variable, (*self.config).catdesc, ATT_VALUE=catdesc)
    if arg_present(delta_minus_var) then void = self -> VarHasAtt(variable, (*self.config).delta_minus_var, ATT_VALUE=delta_minus_var)
    if arg_present(delta_plus_var)  then void = self -> VarHasAtt(variable, (*self.config).delta_plus_var, ATT_VALUE=delta_plus_var)
    if arg_present(depend_0)   then void = self -> VarHasAtt(variable, (*self.config).depend_0, ATT_VALUE=depend_0)
    if arg_present(depend_1)   then void = self -> VarHasAtt(variable, (*self.config).depend_1, ATT_VALUE=depend_1)
    if arg_present(depend_2)   then void = self -> VarHasAtt(variable, (*self.config).depend_2, ATT_VALUE=depend_2)
    if arg_present(depend_3)   then void = self -> VarHasAtt(variable, (*self.config).depend_3, ATT_VALUE=depend_3)
    if arg_present(display_type) then void = self -> VarHasAtt(variable, (*self.config).display_type, ATT_VALUE=display_type)
    if arg_present(fieldnam)   then void = self -> VarHasAtt(variable, (*self.config).fieldnam, ATT_VALUE=fieldnam)
    if arg_present(fillval)    then void = self -> VarHasAtt(variable, (*self.config).fillval, ATT_VALUE=fillval)
    if arg_present(lablaxis)   then void = self -> VarHasAtt(variable, (*self.config).lablaxis, ATT_VALUE=lablaxis)
    if arg_present(labl_ptr_1) then void = self -> VarHasAtt(variable, (*self.config).labl_ptr_1, ATT_VALUE=labl_ptr_1)
    if arg_present(labl_ptr_2) then void = self -> VarHasAtt(variable, (*self.config).labl_ptr_2, ATT_VALUE=labl_ptr_2)
    if arg_present(labl_ptr_3) then void = self -> VarHasAtt(variable, (*self.config).labl_ptr_3, ATT_VALUE=labl_ptr_3)
    if arg_present(units)      then void = self -> VarHasAtt(variable, (*self.config).units, ATT_VALUE=units)
    if arg_present(unit_ptr)   then void = self -> VarHasAtt(variable, (*self.config).unit_ptr, ATT_VALUE=unit_ptr)
    if arg_present(validmin)   then void = self -> VarHasAtt(variable, (*self.config).validmin, ATT_VALUE=validmin)
    if arg_present(validmax)   then void = self -> VarHasAtt(variable, (*self.config).validmax, ATT_VALUE=validmax)
    if arg_present(var_notes)  then void = self -> VarHasAtt(variable, (*self.config).var_notes, ATT_VALUE=var_notes)
    if arg_present(var_type)   then void = self -> VarHasAtt(variable, (*self.config).var_type, ATT_VALUE=var_type)
    if arg_present(scaletyp)   then void = self -> VarHasAtt(variable, (*self.config).scaletyp, ATT_VALUE=scaletyp)
    if arg_present(scal_ptr)   then void = self -> VarHasAtt(variable, (*self.config).scal_ptr, ATT_VALUE=scal_ptr)
    if arg_present(scalemin)   then void = self -> VarHasAtt(variable, (*self.config).scalemin, ATT_VALUE=scalemin)
    if arg_present(scalemax)   then void = self -> VarHasAtt(variable, (*self.config).scalemax, ATT_VALUE=scalemax)
end


;+
;   The purpose of this method is to get the names of the variable attributes in the
;   current configuration. Definitions of variable attributes can be found at the NASA
;   Goddard's Space Flight Data Facility  `International Solar-Terrestrial Physics 
;   <http://spdf.gsfc.nasa.gov/istp_guide/istp_guide.html>` webpage.
;
;   Some CDF files do not follow the attribute naming conventions found on the webpage,
;   so this method offers a simple way of changing them.
;-
pro CDF_Read::Get_Config, $
CATDESC = catdesc, $
DELTA_MINUS_VAR = delta_minus_var, $
DELTA_PLUS_VAR = delta_plus_var, $
DEPEND_0 = depend_0, $
DEPEND_1 = depend_1, $
DEPEND_2 = depend_2, $
DEPEND_3 = depend_3, $
DISPLAY_TYPE = display_type, $
FIELDNAM = fieldnam, $
FILLVAL = fillval, $
LABL_AXIS = labl_axis, $
LABL_PTR_1 = labl_ptr_1, $
LABL_PTR_2 = labl_ptr_2, $
LABL_PTR_3 = labl_ptr_3, $
UNITS = units, $
UNIT_PTR = unit_ptr, $
VALIDMIN = validmin, $
VALIDMAX = validmax, $
VAR_NOTES = var_notes, $
VAR_TYPE = var_type, $
SCALETYP = scaletyp, $
SCAL_PTR = scal_ptr, $
SCALEMIN = scalemin, $
SCALEMAX = scalemax
    compile_opt idl2
    on_error, 2

    ;Set the variable attribute names.
    if arg_present(catdesc) ne 0 then catdesc = (*self.config).catdesc
    if arg_present(delta_minus_var) ne 0 then delta_minus_var = (*self.config).delta_minus_var
    if arg_present(delta_plus_var) ne 0 then delta_plus_var = (*self.config).delta_plus_var
    if arg_present(depend_0) ne 0 then depend_0 = (*self.config).depend_0
    if arg_present(depend_1) ne 0 then depend_1 = (*self.config).depend_1
    if arg_present(depend_2) ne 0 then depend_2 = (*self.config).depend_2
    if arg_present(depend_3) ne 0 then depend_3 = (*self.config).depend_3
    if arg_present(display_type) ne 0 then display_type = (*self.config).display_type
    if arg_present(fieldnam) ne 0 then fieldnam = (*self.config).fieldnam
    if arg_present(fillval) ne 0 then fillval = (*self.config).fillval
    if arg_present(labl_ptr_1) ne 0 then labl_ptr_1 = (*self.config).labl_ptr_1
    if arg_present(labl_ptr_2) ne 0 then labl_ptr_2 = (*self.config).labl_ptr_2
    if arg_present(labl_ptr_3) ne 0 then labl_ptr_3 = (*self.config).labl_ptr_3
    if arg_present(units) ne 0 then units = (*self.config).units
    if arg_present(unit_ptr) ne 0 then unit_ptr = (*self.config).unit_ptr
    if arg_present(validmin) ne 0 then validmin = (*self.config).validmin
    if arg_present(validmax) ne 0 then validmax = (*self.config).validmax
    if arg_present(var_notes) ne 0 then var_notes = (*self.config).var_notes
    if arg_present(var_type) ne 0 then var_type = (*self.config).var_type
    if arg_present(scaletyp) ne 0 then scaletyp = (*self.config).scaletyp
    if arg_present(scal_ptr) ne 0 then scal_ptr = (*self.config).scal_ptr
    if arg_present(scalemin) ne 0 then scalemin = (*self.config).scalemin
    if arg_present(scalemax) ne 0 then scalemax = (*self.config).scalemax
    
end


;+
;   The purpose of this method is to set the names of the variable attributes.
;   Definitions of variable attributes can be found at the NASA Goddard's
;   Space Flight Data Facility  `International Solar-Terrestrial Physics 
;   <http://spdf.gsfc.nasa.gov/istp_guide/istp_guide.html>` webpage.
;
;   Some CDF files do not follow the attribute naming conventions found on the webpage,
;   so this method offers a simple way of changing them.
;-
pro CDF_Read::set_config, $
CLUSTER = cluster, $

CATDESC = catdesc, $
DEPEND_0 = depend_0, $
DEPEND_1 = depend_1, $
DEPEND_2 = depend_2, $
DEPEND_3 = depend_3, $
DELTA_MINUS_VAR = delta_minus_var, $
DELTA_PLUS_VAR = delta_plus_var, $
DISPLAY_TYPE = display_type, $
FIELDNAM = fieldnam, $
FILLVAL = fillval, $
LABL_AXIS = labl_axis, $
LABL_PTR_1 = labl_ptr_1, $
LABL_PTR_2 = labl_ptr_2, $
LABL_PTR_3 = labl_ptr_3, $
UNITS = units, $
UNIT_PTR = unit_ptr, $
VALIDMIN = validmin, $
VALIDMAX = validmax, $
VAR_NOTES = var_notes, $
VAR_TYPE = var_type, $
SCALETYP = scaletyp, $
SCAL_PTR = scal_ptr, $
SCALEMIN = scalemin, $
SCALEMAX = scalemax
    compile_opt idl2
    on_error, 2
    
    ;Configuration for the CLUSTER satellite mission
    if keyword_set(cluster) then begin
        (*self.config).delta_minus_var = 'DELTA_MINUS'
        (*self.config).delta_plus_var  = 'DELTA_PLUS'
        (*self.config).display_type    = 'DISPLAYTYPE'
        (*self.config).labl_ptr_1      = 'REPRESENTATION_1'
        (*self.config).labl_ptr_2      = 'REPRESENTATION_2'
        (*self.config).labl_ptr_3      = 'REPRESENTATION_3'
        (*self.config).var_type        = 'PARAMETER_TYPE'
    endif

    ;Set the variable attribute names.
    if n_elements(catdesc)         ne 0 then (*self.config).catdesc         = catdesc
    if n_elements(depend_0)        ne 0 then (*self.config).depend_0        = depend_0
    if n_elements(depend_1)        ne 0 then (*self.config).depend_1        = depend_1
    if n_elements(depend_2)        ne 0 then (*self.config).depend_2        = depend_2
    if n_elements(depend_3)        ne 0 then (*self.config).depend_3        = depend_3
    if n_elements(delta_minus_var) ne 0 then (*self.config).delta_minus_var = delta_minus_var
    if n_elements(delta_plus_var)  ne 0 then (*self.config).delta_plus_var  = delta_plus_var
    if n_elements(display_type)    ne 0 then (*self.config).display_type    = display_type
    if n_elements(fieldnam)        ne 0 then (*self.config).fieldnam        = fieldnam
    if n_elements(fillval)         ne 0 then (*self.config).fillval         = fillval
    if n_elements(labl_ptr_1)      ne 0 then (*self.config).labl_ptr_1      = labl_ptr_1
    if n_elements(labl_ptr_2)      ne 0 then (*self.config).labl_ptr_2      = labl_ptr_2
    if n_elements(labl_ptr_3)      ne 0 then (*self.config).labl_ptr_3      = labl_ptr_3
    if n_elements(units)           ne 0 then (*self.config).units           = units
    if n_elements(unit_ptr)        ne 0 then (*self.config).unit_ptr        = unit_ptr
    if n_elements(validmin)        ne 0 then (*self.config).validmin        = validmin
    if n_elements(validmax)        ne 0 then (*self.config).validmax        = validmax
    if n_elements(var_notes)       ne 0 then (*self.config).var_notes       = var_notes
    if n_elements(var_type)        ne 0 then (*self.config).var_type        = var_type
    if n_elements(scaletyp)        ne 0 then (*self.config).scaletyp        = scaletyp
    if n_elements(scal_ptr)        ne 0 then (*self.config).scal_ptr        = scal_ptr
    if n_elements(scalemin)        ne 0 then (*self.config).scalemin        = scalemin
    if n_elements(scalemax)        ne 0 then (*self.config).scalemax        = scalemax
end


;+
;   The purpose of this method is to create a structure containing the properly defined
;   variable attributes. If an older or non-standard CDF file has used different variable,
;   attribute names, these can be changed here without having to alter the program.
;
;   Definitions of variable attributes can be found `here <http://spdf.gsfc.nasa.gov/istp_guide/istp_guide.html>`
;-
pro CDF_Read::configure
    compile_opt idl2
    on_error, 2
    
    self.config = ptr_new({CATDESC: 'CATDESC', $
                           DELTA_MINUS_VAR: 'DELTA_MINUS_VAR', $
                           DELTA_PLUS_VAR: 'DELTA_PLUS_VAR', $
                           DEPEND_0: 'DEPEND_0', $
                           DEPEND_1: 'DEPEND_1', $
                           DEPEND_2: 'DEPEND_2', $
                           DEPEND_3: 'DEPEND_3', $
                           DISPLAY_TYPE: 'DISPLAY_TYPE', $
                           FIELDNAM: 'FIELDNAM', $
                           FILLVAL: 'FILLVAL', $
                           LABLAXIS: 'LABLAXIS', $
                           LABL_PTR_1: 'LABL_PTR_1', $
                           LABL_PTR_2: 'LABL_PTR_2', $
                           LABL_PTR_3: 'LABL_PTR_3', $
                           UNITS: 'UNITS', $
                           UNIT_PTR: 'UNIT_PTR', $
                           VALIDMIN: 'VALIDMIN', $
                           VALIDMAX: 'VALIDMAX', $
                           VAR_NOTES: 'VAR_NOTES', $
                           VAR_TYPE: 'VAR_TYPE', $
                           SCALETYP: 'SCALETYP', $
                           SCAL_PTR: 'SCAL_PTR', $
                           SCALEMIN: 'SCALEMIN', $
                           SCALEMAX: 'SCALEMAX'})
end


;+
;   The purpose of this method is to clean up after the object is destroyed. If a CDF_Read
;   GUI is open, it will also be destroyed.
;-
pro CDF_Read::Cleanup
	compile_opt idl2
    
    ;Catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

    ;Free up pointers associated with the gui 
    ptr_free, self.config
    
    ;Clean up the superclasses
    self -> CDF_Info::Cleanup
end


;+
;   The purpose of this method is to initialize the CDF_Read object reference.
;
; :Params:
;       FILENAME:           in, out, optional, type=string
;                           the filename of the CDF file to be opened
;
; :Keywords:
;       GROUP_LEADER:   in, optional, type=integer
;                       The group leader of the file selection gui. Use only when
;                           `FILENAME` undefined.
;       _REF_EXTRA:     in, optional, type=any
;                       All keyword accepted by the SET_CONFIG method are also excepted
;                           for keyword inheritance.
;
; :Returns:
;       object reference
;-
function CDF_Read::init, filename, $
GROUP_LEADER = group_leader, $
_REF_EXTRA = extra
    compile_opt idl2
    
    ;catch errors
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif

    ;Open the filename, if one was given
    success = self -> cdf_info::init(filename, DIALOG_PARENT=group_leader)
    if success eq 0 then return, 0
    
    ;If this is Cluster data, set the flag.
    if stregex(self.filename, 'C[1-4]', /BOOLEAN) then cluster = 1 else cluster = 0
    
    ;Configure the variable attribute list
    self -> configure
    if (n_elements(extra) ne 0) then self -> set_config, CLUSTER=cluster, _STRICT_EXTRA=extra
    
    return, 1
end


;+
;   Define the CDF_Read class
;
; :Params:
;       CLASS:          out, optional, type=structure
;                       The class definition structure.
;
; :Fields:
;       CONFIG:         Contains CDF attribute information. Some CDF files do not adhere
;                           strictly to the ISTP Guidelines. This allows for flexibility.
;-
pro CDF_Read__define, class
	compile_opt idl2
	
	class = { cdf_read, $
			  inherits cdf_info, $
	          config: ptr_new()  $          ;Variable attributes configuration
	        }
end